﻿using System;
using System.Collections.Generic;
using System.Text;
using ThomasGIS.Geometries;
using ThomasGIS.Helpers;
using System.Linq;
using ThomasGIS.Network;

namespace ThomasGIS.SpatialIndex
{
    public class GridSpatialIndex : ISpatialIndex
    {
        private double xmin;
        private double xmax;
        private double ymin;
        private double ymax;

        private int rows;
        private int cols;

        private int preLocation = 0;

        private double scale;

        private HashSet<int>[,] spatialIndexContainer = null;

        private List<IGeometryBase> innerData = null;

        public int GeometryCount => innerData.Count;

        public GridSpatialIndex(BoundaryBox boundary, double scale)
        {
            int rows = (int)((boundary.YMax - boundary.YMin) / scale + 1);
            int cols = (int)((boundary.XMax - boundary.XMin) / scale + 1);

            this.xmin = boundary.XMin;
            this.xmax = boundary.XMax;
            this.ymin = boundary.YMin;
            this.ymax = boundary.YMax;
            this.scale = scale;
            this.rows = rows;
            this.cols = cols;

            if (rows <= 0 || cols <= 0) throw new Exception("索引的空间范围错误，请检查boundary参数是否合法！");

            spatialIndexContainer = new HashSet<int>[rows, cols];

            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    spatialIndexContainer[i,j] = new HashSet<int>();
                }
            }

            this.innerData = new List<IGeometryBase>();

            this.preLocation = 0;
        }

        // 依据表
        public bool RefreshIndex()
        {
            // 只向后更新未添加过的即可
            for (int i = preLocation; i < this.innerData.Count; i++)
            {
                IGeometryBase geometry = this.innerData[i];
                if (geometry.GetType() == typeof(Point) || geometry.GetType() == typeof(GeoNetworkNode))
                {
                    IPoint point = geometry as IPoint;
                    int xLoc = (int)((point.GetX() - xmin) / scale);
                    int yLoc = (int)((point.GetY() - ymin) / scale);
                    if (xLoc < 0 || xLoc >= this.cols || yLoc < 0 || yLoc >= this.rows) continue;
                    this.spatialIndexContainer[yLoc, xLoc].Add(i);
                }

                if (geometry.GetType() == typeof(SingleLine) || geometry.GetType() == typeof(GeoNetworkArc))
                {
                    ISingleLine singleLine = geometry as ISingleLine;

                    int startLocX = (int)((singleLine.GetStartPoint().GetX() - xmin) / scale);
                    int startLocY = (int)((singleLine.GetStartPoint().GetY() - ymin) / scale);
                    int endLocX = (int)((singleLine.GetEndPoint().GetX() - xmin) / scale);
                    int endLocY = (int)((singleLine.GetEndPoint().GetY() - ymin) / scale);

                    IEnumerable<Location> fillLocations = GISCGCalculator.DigitalDifferentialAnalyzer(startLocX, startLocY, endLocX, endLocY);

                    foreach (Location location in fillLocations)
                    {
                        if (location.X < 0 || location.X >= this.cols || location.Y < 0 || location.Y >= this.rows) continue;
                        this.spatialIndexContainer[location.Y, location.X].Add(i);
                    }
                }

                if (geometry.GetType() == typeof(Polyline))
                {
                    IPolyline polyline = geometry as IPolyline;
                    List<Point> PointList = polyline.GetPointEnumerable().ToList();
                    List<int> PartList = polyline.GetPartEnumerable().ToList();

                    for (int j = 0; j < polyline.GetPartNumber(); j++)
                    {
                        int startLoc = PartList[j];
                        int endLoc;
                        if (j == polyline.GetPartNumber() - 1)
                        {
                            endLoc = polyline.GetPointNumber();
                        }
                        else
                        {
                            endLoc = PartList[j + 1];
                        }

                        for (int k = startLoc; k < endLoc - 1; k++)
                        {
                            Point startPoint = PointList[k];
                            Point endPoint = PointList[k + 1];

                            int startLocX = (int)((startPoint.X - xmin) / scale);
                            int startLocY = (int)((startPoint.Y - ymin) / scale);
                            int endLocX = (int)((endPoint.X - xmin) / scale);
                            int endLocY = (int)((endPoint.Y - ymin) / scale);

                            IEnumerable<Location> fillLocations = GISCGCalculator.DigitalDifferentialAnalyzer(startLocX, startLocY, endLocX, endLocY);

                            foreach (Location location in fillLocations)
                            {
                                if (location.X < 0 || location.X >= this.cols || location.Y < 0 || location.Y >= this.rows) continue;
                                this.spatialIndexContainer[location.Y, location.X].Add(i);
                            }
                        }
                    }
                }

                if (geometry.GetType() == typeof(Polygon))
                {
                    Polygon polygon = geometry as Polygon;

                    // 遍历每一个子部件
                    for (int j = 0; j < polygon.PartNumber; j++)
                    {
                        List<Location> boundaryLocation = new List<Location>();

                        int startLoc = polygon.PartList[j];
                        int endLoc;
                        if (j == polygon.PartNumber - 1)
                        {
                            endLoc = polygon.PointNumber;
                        }
                        else
                        {
                            endLoc = polygon.PartList[j + 1];
                        }

                        // 计算每个子部件的边界Loc坐标（int，int）
                        for (int k = startLoc; k < endLoc; k++)
                        {
                            Point nowPoint = polygon.PointList[k];
                            int locX = (int)((nowPoint.X - this.xmin) / this.scale);
                            int locY = (int)((nowPoint.Y - this.ymin) / this.scale);
                            boundaryLocation.Add(new Location(locX, locY));
                        }

                        // 将边界坐标转换为填充坐标，并按填充坐标填充至空间索引中
                        IEnumerable<Location> fillLocations = GISCGCalculator.PolygonFillLocations(boundaryLocation);
                        foreach (Location location in fillLocations)
                        {
                            if (location.X < 0 || location.X >= this.cols || location.Y < 0 || location.Y >= this.rows) continue;
                            this.spatialIndexContainer[location.Y, location.X].Add(i);
                        }
                    }
                }
            }

            this.preLocation = innerData.Count;

            return true;
        }

        public bool AddItem(IGeometryBase item)
        {
            this.innerData.Add(item);
            return true;
        }

        // 移除对应index的要素
        public bool RemoveItem(int index)
        {
            // 本质上仅移除grid里的index数值，不操作innerData中的index，降低复杂度，即只向innerData中添加
            IGeometryBase geometry = this.innerData[index];
            if (geometry.GetType() == typeof(Point) || geometry.GetType() == typeof(GeoNetworkNode))
            {
                IPoint point = geometry as IPoint;
                int xLoc = (int)((point.GetX() - xmin) / scale);
                int yLoc = (int)((point.GetY() - ymin) / scale);
                if (xLoc < 0 || xLoc >= this.cols || yLoc < 0 || yLoc >= this.rows) return false;
                this.spatialIndexContainer[yLoc, xLoc].Remove(index);
            }

            if (geometry.GetType() == typeof(SingleLine) || geometry.GetType() == typeof(GeoNetworkArc))
            {
                ISingleLine singleLine = geometry as ISingleLine;

                int startLocX = (int)((singleLine.GetStartPoint().GetX() - xmin) / scale);
                int startLocY = (int)((singleLine.GetStartPoint().GetY() - ymin) / scale);
                int endLocX = (int)((singleLine.GetEndPoint().GetX() - xmin) / scale);
                int endLocY = (int)((singleLine.GetEndPoint().GetY() - ymin) / scale);

                IEnumerable<Location> fillLocations = GISCGCalculator.DigitalDifferentialAnalyzer(startLocX, startLocY, endLocX, endLocY);

                foreach (Location location in fillLocations)
                {
                    if (location.X < 0 || location.X >= this.cols || location.Y < 0 || location.Y >= this.rows) continue;
                    this.spatialIndexContainer[location.Y, location.X].Remove(index);
                }
            }


            if (geometry.GetType() == typeof(Polyline))
            {
                Polyline polyline = geometry as Polyline;
                for (int j = 0; j < polyline.PartNumber; j++)
                {
                    int startLoc = polyline.PartList[j];
                    int endLoc;
                    if (j == polyline.PartNumber - 1)
                    {
                        endLoc = polyline.PointNumber;
                    }
                    else
                    {
                        endLoc = polyline.PartList[j + 1];
                    }

                    for (int k = startLoc; k < endLoc - 1; k++)
                    {
                        Point startPoint = polyline.PointList[k];
                        Point endPoint = polyline.PointList[k + 1];

                        int startLocX = (int)((startPoint.X - xmin) / scale);
                        int startLocY = (int)((startPoint.Y - ymin) / scale);
                        int endLocX = (int)((endPoint.X - xmin) / scale);
                        int endLocY = (int)((endPoint.Y - ymin) / scale);

                        IEnumerable<Location> fillLocations = GISCGCalculator.DigitalDifferentialAnalyzer(startLocX, startLocY, endLocX, endLocY);

                        foreach (Location location in fillLocations)
                        {
                            if (location.X < 0 || location.X >= this.cols || location.Y < 0 || location.Y >= this.rows) continue;
                            this.spatialIndexContainer[location.Y, location.X].Remove(index);
                        }
                    }
                }
            }

            if (geometry.GetType() == typeof(SingleLine))
            {
                SingleLine singleLine = geometry as SingleLine;

                int startLocX = (int)((singleLine.StartPoint.X - xmin) / scale);
                int startLocY = (int)((singleLine.StartPoint.Y - ymin) / scale);
                int endLocX = (int)((singleLine.EndPoint.X - xmin) / scale);
                int endLocY = (int)((singleLine.EndPoint.Y - ymin) / scale);

                IEnumerable<Location> fillLocations = GISCGCalculator.DigitalDifferentialAnalyzer(startLocX, startLocY, endLocX, endLocY);

                foreach (Location location in fillLocations)
                {
                    this.spatialIndexContainer[location.Y, location.X].Remove(index);
                }
            }

            if (geometry.GetType() == typeof(Polygon))
            {
                Polygon polygon = geometry as Polygon;

                // 遍历每一个子部件
                for (int j = 0; j < polygon.PartNumber; j++)
                {
                    List<Location> boundaryLocation = new List<Location>();

                    int startLoc = polygon.PartList[j];
                    int endLoc;
                    if (j == polygon.PartNumber - 1)
                    {
                        endLoc = polygon.PointNumber;
                    }
                    else
                    {
                        endLoc = polygon.PartList[j + 1];
                    }

                    // 计算每个子部件的边界Loc坐标（int，int）
                    for (int k = startLoc; k < endLoc; k++)
                    {
                        Point nowPoint = polygon.PointList[k];
                        int locX = (int)((nowPoint.X - this.xmin) / this.scale);
                        int locY = (int)((nowPoint.Y - this.ymin) / this.scale);
                        boundaryLocation.Add(new Location(locX, locY));
                    }

                    // 将边界坐标转换为填充坐标，并按填充坐标填充至空间索引中
                    IEnumerable<Location> fillLocations = GISCGCalculator.PolygonFillLocations(boundaryLocation);
                    foreach (Location location in fillLocations)
                    {
                        if (location.X < 0 || location.X >= this.cols || location.Y < 0 || location.Y >= this.rows) continue;
                        this.spatialIndexContainer[location.Y, location.X].Remove(index);
                    }
                }
            }

            return true;
        }

        public bool RemoveItem(IGeometryBase geometry)
        {
            int index = this.innerData.IndexOf(geometry);
            this.RemoveItem(index);
            return true;
        }

        public IEnumerable<IGeometryBase> SearchItem(IPoint point, double range)
        {
            IEnumerable<int> resultIndexList = SearchID(point, range);

            List<IGeometryBase> result = new List<IGeometryBase>();

            foreach (int index in resultIndexList)
            {
                
                result.Add(this.innerData[index]);
            }

            return result;
        }

        public IEnumerable<int> SearchID(IPoint point, double range)
        {
            if (preLocation != innerData.Count)
            {
                RefreshIndex();
            }

            int locX = (int)((point.GetX() - this.xmin) / this.scale);
            int locY = (int)((point.GetY() - this.ymin) / this.scale);

            int windowSize = (int)(range / this.scale) + 1;

            HashSet<int> result = new HashSet<int>();

            for (int i = -windowSize; i <= windowSize; i++)
            {
                for (int j = -windowSize; j <= windowSize; j++)
                {
                    int nowX = locX + i;
                    int nowY = locY + j;
                    if (nowX < 0 || nowX >= this.cols || nowY < 0 || nowY >= this.rows) continue;
                    result.UnionWith(this.spatialIndexContainer[nowY, nowX]);
                }
            }

            return result;
        }

        public IGeometryBase GetItemByIndex(int index)
        {
            if (index < -this.GeometryCount || index >= this.GeometryCount) throw new Exception("索引超出范围！");

            if (index < 0) index += this.GeometryCount;

            return this.innerData[index];
        }

        public double GetScale()
        {
            return scale;
        }
    }
}
